package com.jj.core.rpc;

import com.alibaba.fastjson.JSON;
import com.jj.aop.annotation.RPC;
import com.jj.message.Result;
import com.jj.net.Context;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

@Component
public class RPCManager extends RPCHandler {
    private static Logger logger = LoggerFactory.getLogger(RPCManager.class);

    @Autowired
    private RPCUtils rpcUtils;

    private Map<Integer, RPCConfig> rpcConfigs = new HashMap<>();

    public void regRPCHandler(RPCHandler handler){
        Class<?> cls = handler.getClass();
        for(Method method : cls.getDeclaredMethods()){
            logger.debug("searching {}.{}", cls.getSimpleName(), method.getName());
            RPC rpc = AnnotationUtils.findAnnotation(method, RPC.class);
            if(rpc != null){
                int id = rpc.id();
                String[] args = rpc.args();
                RPCConfig rpcCfg = new RPCConfig(id,args,method,handler);
                rpcCfg.setParametersType(method.getParameterTypes());
                rpcCfg.generic = rpc.generic();
                rpcConfigs.put(id, rpcCfg);
                logger.info("reg RPC handler {} with {}", id, method);
            }
        }
        handler.setManager(this);
    }

    public boolean hasConfig(int id){
        return rpcConfigs.containsKey(id);
    }

    public RPCUtils getRpcUtils() {
        return rpcUtils;
    }

    public void setRpcUtils(RPCUtils rpcUtils) {
        this.rpcUtils = rpcUtils;
    }

    public Object call(int id, Object[] args, Context fromCtx) throws RPCNotFoundException, RPCException{
        Object retValue = null;
        RPCConfig rpcConfig = rpcConfigs.get(id);
        if(rpcConfig != null){
            ((RPCHandler)rpcConfig.handler).setContext(fromCtx);
            logger.debug("handler {} injected context", rpcConfig.handler);
            try {
                retValue = rpcConfig.method.invoke(rpcConfig.handler, args);
            } catch (Throwable t) {
                logger.error("exception", t);
                Result<String> result = new Result<>();
                result.setCode(1);
                result.setValue("exception:"+t.getMessage());
                retValue = clientManager.msg().onReturn(result);
            }
        }else{
            logger.debug("id {} not foud for parameter convert.", id);
        }

        return retValue;
    }

    public Object call(int id, String jsonMap, Context fromCtx) throws RPCNotFoundException, RPCException{
        RPCConfig rpcConfig = rpcConfigs.get(id);
        if(rpcConfig != null){
            Object[] args = null;
            List<Object> argList = new LinkedList<>();
            //需要根据函数原型入口决定入参
            if(rpcConfig.args != null){
                //根据参数类型转化，如果无参数，置换成null填入
                if(!StringUtils.isEmpty(jsonMap))
                {
                    Map argMap = null;
                    if(rpcConfig.generic == false)
                    {
                        argMap = JSON.parseObject(jsonMap, Map.class);
                        //call with map parameters
                        int parameterIdx = 0;
                        for(String argName :rpcConfig.args){
                            Object parameter = argMap.get(argName);
                            if(parameter != null)
                            {
                                //入参类型不符，需要转换
                                if(parameter.getClass() != rpcConfig.getParametersType()[parameterIdx]){
                                    Object newParam = rpcUtils.convert(
                                            rpcConfig.getParametersType()[parameterIdx],
                                            parameter.toString());
                                    argList.add(newParam);
                                }else{
                                    //类型相同，直接转换
                                    argList.add(parameter);
                                }

                            }else{
                                logger.warn("msgId:{}, parameter not found for {}, using null",
                                        id,argName);
                                argList.add(null);
                            }
                            parameterIdx++;
                        }
                    }else{
                        //泛化GM调用必须用单字符川传入，此处不转义
                        argList.add(jsonMap);
                    }
                }else{
                    //传入null字串，需要置换成null列表作为入参
                    for(String arg : rpcConfig.args){
                        argList.add(null);
                    }
                }
                args = argList.toArray(new Object[0]);
            }else{
                //直接调用，不需要参数
                args = null;
            }
            try {
                ((RPCHandler)rpcConfig.handler).setContext(fromCtx);
                logger.debug("handler {} injected context", rpcConfig.handler);
                return rpcConfig.method.invoke(rpcConfig.handler, args);
            } catch (Throwable t) {
                logger.error("exception" , t);
                Result<String> result = new Result<>();
                result.setCode(1);
                result.setValue("exception:"+t.getMessage());
                return clientManager.msg().onReturn(result);
                //throw new RPCException(t);
            }

//            Map argMap = null;
//            Object[] args = null;
//            if(!StringUtils.isEmpty(jsonMap))
//            {
//                List<Object> argList = new LinkedList<>();
//                if(rpcConfig.generic == false)
//                {
//                    argMap = JSON.parseObject(jsonMap, Map.class);
//                    //call with map parameters
//                    for(String argName :rpcConfig.args){
//                        argList.add(argMap.get(argName));
//                    }
//                }else{
//                    argList.add(jsonMap);
//                }
//                args = argList.toArray(new Object[0]);
//            }
//            try {
//                ((RPCHandler)rpcConfig.handler).setContext(fromCtx);
//                logger.debug("handler {} injected context", rpcConfig.handler);
//                return rpcConfig.method.invoke(rpcConfig.handler, args);
//            } catch (Throwable t) {
//                logger.error("exception" , t);
//                throw new RPCException(t);
//            }
        }else{
            throw new RPCNotFoundException("ID " + id +" not found");
        }
    }

    /**
     * 泛化调用接口
     * @param id
     * @param commands
     * @param cmdOffset, 从command的那个偏移开始
     * @return
     * @throws RPCNotFoundException
     * @throws RPCException
     */
    public Object callGeneric(int id, String[] commands, int cmdOffset, Context context) throws RPCNotFoundException, RPCException{
        RPCConfig rpcConfig = rpcConfigs.get(id);
        if(rpcConfig != null){
            ((RPCHandler)rpcConfig.handler).setContext(context);
            Class<?>[] argsCls = rpcConfig.method.getParameterTypes();
            List<Object> argList = new LinkedList<>();
            for(int i = 0; i<argsCls.length; i++){
                Class<?> cls = argsCls[i];
                if(commands.length>cmdOffset+i){
                    String value = commands[i+cmdOffset];
                    argList.add(rpcUtils.convert(cls,value));
                }else{
                    argList.add(null);
                }
            }

            Object[] args = argList.toArray(new Object[0]);
            try {
                return rpcConfig.method.invoke(rpcConfig.handler, args);
            } catch (Throwable t) {
                logger.error("exception" , t);
                throw new RPCException(t);
            }
        }else{
            throw new RPCNotFoundException("ID " + id +" not found");
        }
    }

    public Object callGeneric(int id, String[] commands, Context context) throws RPCNotFoundException, RPCException{
        return callGeneric(id, commands,0, context);
    }

    private static class RPCConfig{
        public int id;
        public String[] args;
        public Method method;
        public Object handler;
        public Boolean generic = false;
        public Class<?>[] parametersType = null;

        public RPCConfig(int id, String[] args, Method method, Object handler) {
            this.id = id;
            this.args = args;
            this.method = method;
            this.handler = handler;
        }

        public Class<?>[] getParametersType() {
            return parametersType;
        }

        public void setParametersType(Class<?>[] parametersType) {
            this.parametersType = parametersType;
        }
    }

    public static class RPCNotFoundException extends Exception{
        public RPCNotFoundException(String message) {
            super(message);
        }
    }

    public static class RPCException extends Exception{
        public RPCException(Throwable cause) {
            super(cause);
        }
    }

}
